﻿Imports System.IO
Imports System.Text

Namespace Controls
    Public Class ScrollableTextBlock
        Inherits Control
        Private stackPanel As StackPanel
        Private measureBlock As TextBlock

        Public Sub New()
            ' Get the style from generic.xaml
            Me.DefaultStyleKey = GetType(ScrollableTextBlock)
        End Sub

        Public Shared ReadOnly TextProperty As DependencyProperty = DependencyProperty.Register("Text", GetType(String), GetType(ScrollableTextBlock), New PropertyMetadata("ScrollableTextBlock", AddressOf OnTextPropertyChanged))

        Public Property Text() As String
            Get
                Return DirectCast(GetValue(TextProperty), String)
            End Get
            Set(value As String)
                SetValue(TextProperty, value)
            End Set
        End Property

        Private Shared Sub OnTextPropertyChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
            Dim source As ScrollableTextBlock = DirectCast(d, ScrollableTextBlock)
            Dim value As String = DirectCast(e.NewValue, String)
            source.ParseText(value)
        End Sub

        Public Overrides Sub OnApplyTemplate()
            MyBase.OnApplyTemplate()
            Me.stackPanel = TryCast(Me.GetTemplateChild("StackPanel"), StackPanel)
            Me.ParseText(Me.Text)
        End Sub


        Private Sub ParseTextHide(value As String)
            If Me.stackPanel Is Nothing Then
                Return
            End If
            ' Clear previous TextBlocks
            Me.stackPanel.Children.Clear()
            ' Calculate max char count
            Dim maxTexCount As Integer = Me.GetMaxTextSize()

            If value.Length < maxTexCount Then
                Dim textBlock As TextBlock = Me.GetTextBlock()
                textBlock.Text = value
                Me.stackPanel.Children.Add(textBlock)
            Else
                Dim n As Integer = value.Length \ maxTexCount
                Dim start As Integer = 0

                ' Add textblocks
                For i As Integer = 0 To n - 1
                    Dim textBlock As TextBlock = Me.GetTextBlock()
                    textBlock.Text = value.Substring(start, maxTexCount)
                    Me.stackPanel.Children.Add(textBlock)
                    start = maxTexCount
                Next

                ' Pickup the leftover text
                If value.Length Mod maxTexCount > 0 Then
                    Dim textBlock As TextBlock = Me.GetTextBlock()
                    textBlock.Text = value.Substring(maxTexCount * n, value.Length - maxTexCount * n)
                    Me.stackPanel.Children.Add(textBlock)
                End If
            End If
        End Sub

        Private Sub ParseText(value As String)
            Dim reader As New StringReader(value)

            If Me.stackPanel Is Nothing Then
                Return
            End If
            ' Clear previous TextBlocks
            Me.stackPanel.Children.Clear()
            ' Calculate max char count
            Dim maxTexCount As Integer = Me.GetMaxTextSize()

            If value.Length < maxTexCount Then
                Dim textBlock As TextBlock = Me.GetTextBlock()
                textBlock.Text = value
                Me.stackPanel.Children.Add(textBlock)
            Else
                Dim line As String = ""

                While reader.Peek() > 0
                    line = reader.ReadLine()
                    ParseLine(line)

                End While
            End If
        End Sub

        Private Sub ParseLine(line As String)
            Dim lineCount As Integer = 0
            Dim maxLineCount As Integer = GetMaxLineCount()
            Dim tempLine As String = line
            Dim sbLine As New StringBuilder()

            While lineCount < maxLineCount
                Dim charactersFitted As Integer = MeasureString(tempLine, CInt(Me.Width))
                Dim leftSide As String = tempLine.Substring(0, charactersFitted)
                sbLine.Append(leftSide)
                tempLine = tempLine.Substring(charactersFitted, tempLine.Length - (charactersFitted))
                lineCount += 1
            End While

            Dim textBlock As TextBlock = Me.GetTextBlock()
            textBlock.Text = sbLine.ToString()
            Me.stackPanel.Children.Add(textBlock)

            If tempLine.Length > 0 Then
                ParseLine(tempLine)
            End If
        End Sub

        Private Function MeasureString(text As String, desWidth As Integer) As Integer

            Dim nWidth As Integer = 0
            Dim charactersFitted As Integer = 0

            Dim sb As New StringBuilder()

            'get original size
            Dim size As Size = MeasureString(text)

            If size.Width > desWidth Then
                Dim words As String() = text.Split(" "c)
                sb.Append(words(0))

                For i As Integer = 1 To words.Length - 1
                    sb.Append(" " & words(i))
                    nWidth = CInt(MeasureString(sb.ToString()).Width)

                    If nWidth > desWidth Then

                        sb.Remove(sb.Length - words(i).Length, words(i).Length)
                        Exit For
                    End If
                Next

                charactersFitted = sb.Length
            Else
                charactersFitted = text.Length
            End If

            Return charactersFitted
        End Function

        Private Function MeasureString(text As String) As Size
            If Me.measureBlock Is Nothing Then
                Me.measureBlock = Me.GetTextBlock()
            End If

            Me.measureBlock.Text = text
            Return New Size(measureBlock.ActualWidth, measureBlock.ActualHeight)
        End Function

        Private Function GetMaxTextSize() As Integer
            ' Get average char size
            Dim size As Size = Me.MeasureText(" ")
            ' Get number of char that fit in the line
            Dim charLineCount As Integer = CInt(Me.Width / size.Width)
            ' Get line count
            Dim lineCount As Integer = CInt(2048 / size.Height)

            Return charLineCount * lineCount \ 2
        End Function

        Private Function GetMaxLineCount() As Integer
            Dim size As Size = Me.MeasureText(" ")
            ' Get number of char that fit in the line
            Dim charLineCount As Integer = CInt(Me.Width / size.Width)
            ' Get line count
            Dim lineCount As Integer = CInt(2048 / size.Height) - 5

            Return lineCount
        End Function

        Private Function GetTextBlock() As TextBlock
            Dim textBlock As New TextBlock()
            textBlock.TextWrapping = TextWrapping.Wrap
            textBlock.FontSize = Me.FontSize
            textBlock.FontFamily = Me.FontFamily
            ' textBlock.FontStyle = this.FontStyle;
            textBlock.FontWeight = Me.FontWeight
            textBlock.Foreground = Me.Foreground
            textBlock.Margin = New Thickness(0, 0, MeasureText(" ").Width, 0)
            Return textBlock
        End Function

        Private Function MeasureText(value As String) As Size
            Dim textBlock As TextBlock = Me.GetTextBlockOnly()
            textBlock.Text = value
            Return New Size(textBlock.ActualWidth, textBlock.ActualHeight)
        End Function

        Private Function GetTextBlockOnly() As TextBlock
            Dim textBlock As New TextBlock()
            textBlock.TextWrapping = TextWrapping.Wrap
            textBlock.FontSize = Me.FontSize
            textBlock.FontFamily = Me.FontFamily
            textBlock.FontWeight = Me.FontWeight
            Return textBlock
        End Function
    End Class
End Namespace
